package com.repeat.submit.filter;

import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONUtil;
import com.repeat.submit.annotation.RepeatSubmit;
import com.repeat.submit.utils.RedisUtils;
import io.netty.buffer.EmptyByteBuf;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author xiagen
 * @date 2021/6/22
 */
@Slf4j
public class RepeatSubmitFilter implements WebFilter, Ordered {

    private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();

    private String tokenName;

    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    private RedisUtils redisUtils;

    public RepeatSubmitFilter(String tokenName, RequestMappingHandlerMapping requestMappingHandlerMapping,RedisUtils redisUtils) {
        this.tokenName = tokenName;
        this.requestMappingHandlerMapping = requestMappingHandlerMapping;
        this.redisUtils = redisUtils;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
        HandlerMethod handlerMethod = (HandlerMethod) requestMappingHandlerMapping.getHandler(serverWebExchange).toProcessor().peek();
        String contentType = Optional.ofNullable(serverWebExchange.getRequest().getHeaders().getContentType()).map(ctx -> ctx.toString()).orElse("");
        String token = serverWebExchange.getRequest().getHeaders().getFirst(tokenName);
        StringBuffer repeatKey = new StringBuffer();
        if (Objects.nonNull(handlerMethod) && handlerMethod.hasMethodAnnotation(RepeatSubmit.class) && contentType.contains(MediaType.APPLICATION_JSON_VALUE) && serverWebExchange.getRequest().getMethod().matches("POST") && !StringUtils.isEmpty(token)){
            RepeatSubmit repeatSubmit = handlerMethod.getMethodAnnotation(RepeatSubmit.class);
            repeatKey.append(handlerMethod.getBeanType().getName())
                    .append(".")
                    .append(handlerMethod.getMethod().getName())
                    .append(".")
                    .append(token);
            NettyDataBufferFactory factory = (NettyDataBufferFactory) serverWebExchange.getResponse().bufferFactory();
            return DataBufferUtils.join(serverWebExchange.getRequest().getBody().defaultIfEmpty(factory.wrap(new EmptyByteBuf(factory.getByteBufAllocator())))).flatMap(dataBuffer -> {
                // 创建一个容量为dataBuffer容量大小的字节数组
                byte[] bytes = new byte[dataBuffer.readableByteCount()];
                // dataBuffer类容读取到bytes中
                dataBuffer.read(bytes);
                // 释放缓冲区
                DataBufferUtils.release(dataBuffer);
                // 创建新缓冲区并写入数据
                Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                    DataBuffer buffer = factory.wrap(bytes);
                    DataBufferUtils.retain(buffer);
                    return Mono.just(buffer);
                });
                // 由于原来的request请求参数被消费，需要提供新的请求
                ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(serverWebExchange.getRequest()) {
                    @Override
                    public Flux<DataBuffer> getBody() {
                        return cachedFlux;
                    }
                };
                // 创建新的exchange并构建解析的数据
                ServerWebExchange mutatedExchange = serverWebExchange.mutate().request(mutatedRequest).build();
                return ServerRequest.create(mutatedExchange, messageReaders)
                        .bodyToMono(String.class)
                        .flatMap(objectValue -> {
                            if (!StringUtils.isEmpty(objectValue)){
                                repeatKey.append(".").append(objectValue);
                            }
                            String repeatKeyValue = generateRepeatKey(repeatKey.toString());
                            String value = UUID.randomUUID().toString();

                            return check(mutatedExchange,webFilterChain,repeatKeyValue,value,repeatSubmit.expiration());

                        });
            });

        }
        return doFilter(serverWebExchange,webFilterChain,repeatKey.toString(),UUID.randomUUID().toString());
    }

    private Mono<Void> doFilter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain,String repeatKeyValue,String value){
        return webFilterChain.filter(serverWebExchange).doFinally(signalType -> {
            redisUtils.deleteKey(repeatKeyValue,value);
        });
    }


    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }

    private String generateRepeatKey(String repeatKeyValue){
        return SecureUtil.md5(repeatKeyValue);
    }

    private Mono<Void> check(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain, String repeatKeyValue, String value, long s){
        boolean flag = redisUtils.setNxKey(repeatKeyValue, value, s);
        if (!flag){
            log.info("重复提交key:{}",repeatKeyValue);
            Map<String, Object> map = new HashMap<>(2);
            map.put("code",-2);
            map.put("msg","请勿重复提交");
            String jsonStr = JSONUtil.toJsonStr(map);
            ServerHttpResponse response = serverWebExchange.getResponse();
            response.setStatusCode(HttpStatus.OK);
            response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
            DataBuffer buffer = response.bufferFactory().wrap(jsonStr.getBytes(StandardCharsets.UTF_8));
            return response.writeWith(Mono.just(buffer));
        }
        log.info("生成防重复提交key:{},value:{}",repeatKeyValue,value);
        return doFilter(serverWebExchange,webFilterChain,repeatKeyValue, value);
    }
}
